home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Utilities / Workspace / Locus / Source / Folder.m < prev    next >
Text File  |  1995-06-12  |  23KB  |  1,132 lines

  1.  
  2. /*
  3.     Copyright 1993  Jeremy Slade.
  4.  
  5.     You are free to use all or any parts of the Locus project
  6.     however you wish, just give credit where credit is due.
  7.     The author (Jeremy Slade) shall not be held responsible
  8.     for any damages that result out of use or misuse of any
  9.     part of this project.
  10.  
  11. */
  12.  
  13. /*
  14.     Project: Locus
  15.     
  16.     Class: Folder
  17.     
  18.     Description: See Folder.h
  19.  
  20.     Original Author: Jeremy Slade
  21.     
  22.     Revision History:
  23.         Created
  24.             V.101    JGS Wed Feb  3 23:26:29 GMT-0700 1993
  25.  
  26. */
  27.  
  28. #import "Folder.h"
  29.  
  30. #import "FolderController.h"
  31. #import "FolderViewer.h"
  32. #import "Globals.h"
  33. #import "Group.h"
  34. #import "GroupBrowser.h"
  35. #import "Inspector.h"
  36. #import "ItemCell.h"
  37.  
  38. #import <libc.h>
  39. #import <stdio.h>
  40. #import <sys/dir.h>
  41. #import <sys/param.h>
  42. #import <sys/types.h>
  43.  
  44.  
  45. // When frame.origin.x is set to NEEDS_NEW_VIEWER, the -obtainViewer
  46. // method uses this to know that the folder has not yet been given a viewer.
  47. #define NEEDS_NEW_VIEWER    (-100000)
  48.  
  49. #define MAX_TAG        (99999)
  50.  
  51.  
  52. // This macro defines the conditions for when the FolderViewer will be
  53. // marked as changed -- i.e. when the X in the close button will show
  54. // as broken
  55. #define LOOKS_CHANGED \
  56.     (([changedGroups count] || (fFlags.removedGroup && [self count])) \
  57.         ? YES : NO)
  58.  
  59.  
  60.  
  61. @implementation Folder
  62.  
  63.  
  64. // -------------------------------------------------------------------------
  65. //   Creating, initializing Methods
  66. // -------------------------------------------------------------------------
  67.  
  68.  
  69. + initialize
  70. {
  71.     [self setVersion:Folder_VERSION];
  72.     return ( self );
  73. }
  74.  
  75.  
  76.  
  77. - initCount:(unsigned int)numSlots
  78. {
  79.     [super initCount:numSlots];
  80.     
  81.     currentGroup = nil;
  82.     currentGroupTag = 0;
  83.     filename = NULL;
  84.     
  85.     changedGroups = [[List alloc] initCount:0];
  86.     
  87.     frameRect.origin.x = NEEDS_NEW_VIEWER;
  88.     viewerNum = 0;
  89.     
  90.     fFlags.isChanged = NO;
  91.     fFlags.removedGroup = NO;
  92.     fFlags.needsLoadBrowser = YES;
  93.     fFlags.needsSort = NO;
  94.     fFlags.needsShow = YES;
  95.     fFlags.autoDisplay = YES;
  96.  
  97.     return ( self );
  98. }
  99.  
  100.  
  101.  
  102. - free
  103. {
  104.     if ( filename ) NX_FREE ( filename );
  105.     [changedGroups free];
  106.     [viewer free];
  107.     return ( [super free] );
  108. }
  109.  
  110.  
  111.  
  112. // -------------------------------------------------------------------------
  113. //   Filename
  114. // -------------------------------------------------------------------------
  115.  
  116.  
  117. - setFilename:(const char *)path
  118. {
  119.     if ( filename ) {
  120.         if ( viewer ) [NXApp removeWindowsItem:viewer];
  121.         NX_FREE ( filename );
  122.     }
  123.     filename = path ? NXCopyStringBuffer ( path ) : NULL;
  124.     [self setNeedsShow:YES];
  125.     return ( self );
  126. }
  127.  
  128.  
  129.  
  130. - (const char *)filename
  131. {
  132.     return ( filename );
  133. }
  134.  
  135.  
  136.  
  137. // -------------------------------------------------------------------------
  138. //   Folder Info
  139. // -------------------------------------------------------------------------
  140.  
  141.  
  142. - readInfo
  143. /*
  144.     Reads the folder info
  145. */
  146. {
  147.     NXTypedStream *stream;
  148.     char infoPath[MAXPATHLEN+1];
  149.     int    newCurrentGroupTag;
  150.     int versionNumber;
  151.     
  152.     sprintf ( infoPath, "%s/%s", filename, FOLDER_INFO );
  153.     
  154.     if ( !(stream = NXOpenTypedStreamForFile ( infoPath, NX_READONLY ) ) ) {
  155.         return ( nil ); // Unable to open stream to infoPath
  156.     }
  157.     
  158.     // Get versionNumber
  159.     NXReadType ( stream, "i", &versionNumber );
  160.     
  161.     // Read folder info according to versionNumber
  162.     if ( versionNumber <= Folder_VERSION ) { // up thru current version
  163.         NXReadTypes ( stream, "i", &newCurrentGroupTag );
  164.         NXReadRect ( stream, &frameRect );
  165.     }
  166.     
  167.     else {    // Unrecognized versionNumber
  168.         [self errMsg:"Folder: Unrecognized version %i in Folder info!\n",
  169.             versionNumber];
  170.     }
  171.     
  172.     NXCloseTypedStream ( stream );
  173.  
  174.     fFlags.replaceGroups = NO; // Make sure this flag is off
  175.     
  176.     [self obtainViewer:nil];
  177.     [self makeCurrentGroup:[self groupWithTag:newCurrentGroupTag]];
  178.             
  179.     return ( self );
  180. }
  181.  
  182.  
  183.  
  184. - writeInfo
  185. /*
  186.     Write the folder's info to FOLDER_INFO
  187. */
  188. {
  189.     NXTypedStream *stream;
  190.     char infoPath[MAXPATHLEN+1];
  191.     int versionNumber;
  192.  
  193.     if ( !filename )
  194.         return ( nil ); // No filename specified
  195.  
  196.     // Make sure the folder directory exists
  197.     if ( access ( filename, F_OK ) != 0 ) {
  198.         mkdir ( filename, DIR_CREATE_MASK ); // Make it
  199.     }
  200.         
  201.     sprintf ( infoPath, "%s/%s", filename, FOLDER_INFO );    
  202.     if ( !(stream = NXOpenTypedStreamForFile ( infoPath, NX_WRITEONLY ) ) ) {
  203.         return ( nil ); // Couldn't open typed stream
  204.     }
  205.  
  206.     [viewer getFrame:&frameRect];
  207.     
  208.     // Write the version number
  209.     versionNumber = Folder_VERSION;
  210.     NXWriteType ( stream, "i", &versionNumber );
  211.     
  212.     // Write the other folder info
  213.     NXWriteTypes ( stream, "i", ¤tGroupTag );
  214.     NXWriteRect ( stream, &frameRect );
  215.     
  216.     NXCloseTypedStream ( stream );
  217.     
  218.     [self setChanged:NO];
  219.     return ( self );
  220. }
  221.  
  222.  
  223.  
  224. // -------------------------------------------------------------------------
  225. //   Displaying
  226. // -------------------------------------------------------------------------
  227.  
  228.  
  229. - becomeKeyFolder
  230. {
  231.     DrawInfo info;
  232.     
  233.     // Enable the group popup-list so it responds to cmd-key events
  234.     [[[viewer groupMenu] itemList] setEnabled:YES];
  235.     
  236.     // Set the currentGroups drawFlags to be active
  237.     if ( currentGroup ) {
  238.         [currentGroup getDrawInfo:&info];
  239.         [ItemCell setDrawInfo:&info];
  240.     }
  241.     
  242.     [self showSelf:self];
  243.     return ( self );
  244. }
  245.  
  246.  
  247. - resignKeyFolder
  248. /*
  249.     Sent when this folder is no longer the keyFolder -- when something else is made the keyFolder.
  250. */
  251. {
  252.     // Disable cmd-keys on the group popup-list, so we no longer respond
  253.     // to cmd-key events.
  254.     [[[viewer groupMenu] itemList] setEnabled:NO];
  255.  
  256.     [self showSelf:self];
  257.     return ( self );
  258. }
  259.  
  260.  
  261. - setNeedsShow:(BOOL)flag
  262. {
  263.     fFlags.needsShow = flag;
  264.     if ( fFlags.autoDisplay )
  265.         [self perform:@selector(showSelf:) with:self
  266.             afterDelay:0 cancelPrevious:YES];
  267.     return ( self );
  268. }
  269.  
  270.  
  271. - (BOOL)needsShow
  272. {
  273.     return ( fFlags.needsShow );
  274. }
  275.  
  276.  
  277. - setAutoDisplay:(BOOL)flag
  278. {
  279.     fFlags.autoDisplay = flag;
  280.     if ( fFlags.autoDisplay && fFlags.needsShow ) [self showSelf:self];
  281.     return ( self );
  282. }
  283.  
  284.  
  285.  
  286. - (BOOL)isAutoDisplay
  287. {
  288.     return ( fFlags.autoDisplay );
  289. }
  290.  
  291.  
  292. - showSelf:sender
  293. /*
  294.     Display the viewer and all it's subviews
  295. */
  296. {
  297.     char path[MAXPATHLEN+1];
  298.     
  299.     // Make sure the folder has a viewer
  300.     if ( !viewer ) [self obtainViewer:nil];
  301.     
  302.     // Only display if we need to...
  303.     if ( !fFlags.needsShow ) return ( self );
  304.  
  305. //if ( DEBUGGING ) printf ( "Folder: showing...\n" );
  306.  
  307.     [viewer disableFlushWindow];
  308.     
  309.     // Sort the groups if needed
  310.     if ( fFlags.needsSort ) [self sortGroups];
  311.     
  312.     // Make sure there is a current group, and display it's name in
  313.     // the groupMenuCover button
  314.     if ( ![self count] ) {
  315.         [[[viewer groupMenuCover] setTitle:"No Groups"] setEnabled:NO];
  316.     } else {
  317.         if ( !currentGroup ) [self makeCurrentGroup:[self objectAt:0]];
  318.         [[[viewer groupMenuCover] setTitle:[currentGroup groupName]]
  319.             setEnabled:YES];
  320.     }
  321.  
  322.     // Load the browser if needed
  323.     if ( fFlags.needsLoadBrowser ) {
  324.         [[viewer browser] showGroup:currentGroup];
  325.         fFlags.needsLoadBrowser = NO;
  326.         [[viewer browser] scrollToSelection]; // make sure the selected item is visible
  327.     }
  328.     
  329.     [viewer setDocEdited:LOOKS_CHANGED];
  330.     
  331.     // Set the viewer's title to show the folder's filename
  332.     if ( filename )
  333.         [viewer setTitleAsFilename:filename];
  334.     else {
  335.         sprintf ( path, "UNTITLED" );
  336.         [viewer setTitleAsFilename:path];
  337.     }
  338.     
  339.     [viewer display];
  340.     [[viewer reenableFlushWindow] flushWindow];
  341.     
  342. //if ( DEBUGGING ) printf ( "    Done.\n" );
  343.     fFlags.needsShow = NO;
  344.     [inspector update];
  345.     return ( self );
  346. }
  347.  
  348.  
  349. #define HORIZ_STEP    24
  350. #define    VERT_STEP    24
  351. - obtainViewer:newViewer
  352. /*
  353.     If newViewer is not nil, replace the current viewer with the newViewer and return.  If folder doesn't already have a viewer, then loads a new Folder.nib to create a new FolderViewer, then sizes and locates it as appropriate.
  354. */
  355. {
  356.     static BOOL firstOne = YES;
  357.  
  358.     // This will always cause a need to be re-shown...
  359.     [self setNeedsShow:YES];
  360.     
  361.     if ( newViewer && newViewer != viewer ) {
  362.         [viewer free]; // Free the old viewer
  363.         viewer = newViewer;
  364.         viewerNum = [[newViewer delegate] viewerNum];
  365.         [viewer setDelegate:self];
  366.         fFlags.needsSort = YES;
  367.     }
  368.  
  369.     if ( viewer ) { // Already has a viewer
  370.         [viewer placeWindow:&frameRect];
  371.         return ( self );
  372.     }
  373.  
  374.     
  375.     /*
  376.         Create a new viewer
  377.     */
  378.     
  379.     // Load a new copy of the nib
  380.     // viewer is automatically set to the loaded window
  381.     [NXApp loadNibSection:"Folder.nib"
  382.         owner:self withNames:NO
  383.         fromZone:[self zone]];
  384.  
  385.     if ( frameRect.origin.x == NEEDS_NEW_VIEWER ) {
  386.         if ( self != [folderController keyFolder] && !firstOne
  387.                 && [folderController keyFolder] )
  388.             [[[folderController keyFolder] viewer] getFrame:&frameRect];
  389.         else
  390.             frameRect.origin = defaultViewerFrame.origin;
  391.  
  392.         // Offset the new viewer from the last one opened
  393.         if ( !firstOne ) {
  394.             frameRect.origin.x += HORIZ_STEP;
  395.             frameRect.origin.y += frameRect.size.height - VERT_STEP
  396.                 - defaultViewerFrame.size.height;
  397.         }
  398.         
  399.         frameRect.size = defaultViewerFrame.size;
  400.     }
  401.  
  402.     firstOne = NO;
  403.     
  404.     [viewer placeWindow:&frameRect];
  405.     [viewer setDelegate:self];
  406.     
  407.     [viewer setHideOnDeactivate:hideDeactive];
  408.     [self sortGroups];    
  409.     NXConvertWinNumToGlobal ( [viewer windowNum], &viewerNum );
  410.     return ( self );
  411. }
  412.  
  413.  
  414.  
  415. - releaseViewer
  416. /*
  417.     Set viewer to nil, but don't free the actual viewer.  This is used when transfering the viewer to a different folder.  Returns our viewer
  418. */
  419. {
  420.     id ret = viewer;
  421.     [viewer setDelegate:nil];
  422.     viewer = nil;
  423.     return ( ret );
  424. }
  425.  
  426.  
  427.  
  428. - viewer
  429. {
  430.     return ( viewer );
  431. }
  432.  
  433.  
  434.  
  435. - (int)viewerNum
  436. {
  437.     return ( viewerNum );
  438. }
  439.  
  440.  
  441.  
  442. - setViewerFrame:(const NXRect *)newFrame
  443. {
  444.     frameRect = *newFrame;
  445.     [self setChanged:YES];
  446.     return ( self );
  447. }
  448.  
  449.  
  450.  
  451. - updateInspector:sender
  452. /*
  453.     Sent by the Inspector when it needs to be updated
  454. */
  455. {
  456.     switch ( [inspector inspectorMode] ) {
  457.         case INSPECT_FOLDER:
  458.             [inspector inspect:self]; break;
  459.         case INSPECT_GROUP:
  460.             [inspector inspect:currentGroup]; break;
  461.         case INSPECT_ITEM:
  462.             [inspector inspect:[[viewer browser] selection]]; break;
  463.         default: break;
  464.     }
  465.     return ( self );
  466. }
  467.  
  468.     
  469.  
  470. // -------------------------------------------------------------------------
  471. //   Change flag
  472. // -------------------------------------------------------------------------
  473.  
  474.  
  475. - setChanged:(BOOL)flag
  476. /*
  477.     Set fFlags.isChanged to flag
  478. */
  479. {
  480.     fFlags.isChanged = flag;
  481.  
  482.     if ( fFlags.isChanged && filename ) {
  483.         [self writeInfo];
  484.         fFlags.isChanged = NO;
  485.     }
  486.     [viewer setDocEdited:LOOKS_CHANGED];
  487.     return ( self );
  488. }
  489.  
  490.  
  491.  
  492. - (BOOL)isChanged
  493. {
  494.     return ( LOOKS_CHANGED );
  495. }
  496.  
  497.  
  498.  
  499. - groupChanged:aGroup
  500. {
  501.     [changedGroups addObjectIfAbsent:aGroup];
  502.     [viewer setDocEdited:LOOKS_CHANGED];
  503.     return ( self );
  504. }
  505.  
  506.  
  507.  
  508. - groupSaved:aGroup
  509. {
  510.     [changedGroups removeObject:aGroup];
  511.     [viewer setDocEdited:LOOKS_CHANGED];
  512.     return ( self );
  513. }
  514.  
  515.  
  516.  
  517. - groupRemoved
  518. {
  519.     fFlags.removedGroup = YES;
  520.     [viewer setDocEdited:LOOKS_CHANGED];
  521.     return ( self );
  522. }
  523.  
  524.  
  525.  
  526. - setNeedsLoadBrowser:(BOOL)flag
  527. {
  528.     fFlags.needsLoadBrowser = flag;
  529.     [self setNeedsShow:YES];
  530.     return ( self );
  531. }
  532.  
  533.  
  534.  
  535. - (BOOL)needsLoadBrowser
  536. {
  537.     return ( fFlags.needsLoadBrowser );
  538. }
  539.  
  540.  
  541.  
  542. - replaceAllGroups
  543. /*
  544.     Sets the replaceGroups flag, which will cause all group files in the folder directory to first be removed before the current versions are written the next time the folder is saved.
  545. */
  546. {
  547.     fFlags.replaceGroups = YES;
  548.     return ( self );
  549. }
  550.  
  551.  
  552.  
  553. // -------------------------------------------------------------------------
  554. //   Groups
  555. // -------------------------------------------------------------------------
  556.  
  557.  
  558. - readGroups
  559. /*
  560.     Read all the group files in the folder directory and add them to self
  561. */
  562. {
  563.     struct direct **namelist;
  564.     int i, count;
  565.     int selectGroups ( struct direct *dp );
  566.     NXTypedStream *stream;
  567.     id group;
  568.     char path[MAXPATHLEN+1];
  569.     
  570.     if ( !filename )
  571.         return ( self );
  572.         
  573.     count = scandir ( filename, &namelist, selectGroups, NULL );
  574.     for ( i=0; i<count; i++ ) {
  575.         sprintf ( path, "%s/%s", filename, namelist[i]->d_name );
  576.         if ( (stream = NXOpenTypedStreamForFile ( path, NX_READONLY ) ) ) {
  577.             if ( (group = NXReadObject ( stream ) ) ) {
  578.                 [self addObject:group];
  579.                 [group setFolder:self];
  580.             } else {
  581.                 [self errMsg:"Folder: unable read archived group from\n\t%s\n",
  582.                     path];
  583.             }
  584.             NXCloseTypedStream ( stream );
  585.         } else {
  586.             [self errMsg:"Folder: unable to open stream to\n\t%s\n", path];
  587.         }
  588.         free ( namelist[i] );
  589.     }
  590.     
  591.     if ( count != -1 ) free ( namelist );
  592.  
  593.     fFlags.replaceGroups = NO; // Make sure this flag is off
  594.     fFlags.needsSort = YES; // Force groups to be sorted
  595.     [self sortGroups];
  596.     
  597.     [self setNeedsShow:YES];
  598.     return ( self );
  599. }
  600.  
  601.  
  602.  
  603. - writeGroups
  604. /*
  605.     Write all the groups to files in the folder directory
  606. */
  607. {
  608.     char rm_groups[MAXPATHLEN+1];
  609.     
  610.     [self writeInfo]; // Always do this when writing groups...
  611.     
  612.     if ( fFlags.replaceGroups ) {
  613.         // Remove all groups before writing current groups
  614.         sprintf ( rm_groups, "/bin/rm -f %s/*.%s", filename, GROUP_EXT );
  615.         system ( rm_groups ); // Remove all the existing group files
  616.         fFlags.replaceGroups = NO; // Make sure it is off now
  617.     }
  618.  
  619.     [self makeObjectsPerform:@selector(writeSelf)];
  620.     
  621.     fFlags.removedGroup = NO;
  622.     [viewer setDocEdited:LOOKS_CHANGED];
  623.     return ( self );
  624. }
  625.  
  626.  
  627.  
  628. - (int)tagFor:group
  629. {
  630.     int newTag = [group tag];
  631.     
  632.     srand ( time ( 0 ) );
  633.     while ( !newTag && [self groupWithTag:newTag] != group ) {
  634.         newTag = (rand() % MAX_TAG) + 1; // Choose a random tag
  635.     }
  636.     return ( newTag );
  637. }
  638.  
  639.  
  640.  
  641. - groupWithTag:(int)tag
  642. {
  643.     int i, count;
  644.     id group;
  645.     
  646.     count = [self count];
  647.     for ( i=0; i<count; i++ ) {
  648.         if ( [(group = [self objectAt:i]) tag] == tag )
  649.             return ( group );
  650.     }
  651.     return ( nil ); // Didn't find a group with that tag
  652. }
  653.  
  654.  
  655.  
  656. - groupMenu
  657. {
  658.     return ( [viewer groupMenu] );
  659. }
  660.  
  661.  
  662.  
  663. - currentGroup
  664. {
  665.     return ( currentGroup );
  666. }
  667.  
  668.  
  669.  
  670. - newGroup:sender
  671. /*
  672.     Create a new group and make it the currentGroup
  673. */
  674. {
  675.     int i;
  676.     char name[20];
  677.     
  678.     id newGroup = [Group new];
  679.     
  680.     // Determine a new unique name
  681.     i = 1;
  682.     do
  683.         sprintf ( name, "UNNAMED%d", i++ );
  684.     while ( [self groupCalled:name] );
  685.     
  686.     [newGroup setGroupName:name];
  687.     [newGroup setFolder:self];
  688.     
  689.     fFlags.needsSort = YES;
  690.     [self setNeedsShow:YES];
  691.     
  692.     [self addObject:newGroup];
  693.     [self groupChanged:newGroup];
  694.     [self makeCurrentGroup:newGroup];
  695.  
  696.     // Bring up inspector on new group and select the group name field
  697.     [inspector setInspectorMode:INSPECT_GROUP];
  698.     [inspector inspect:newGroup];
  699.     [[inspector panel] makeKeyWindow];
  700.     [inspector selectGroupName:self];
  701.     
  702.     return ( self );
  703. }
  704.  
  705.  
  706.  
  707. - deleteCurrentGroup:sender
  708. /*
  709.     Delete the currentGroup
  710. */
  711. {
  712.     int i;
  713.     id group = currentGroup;
  714.     char path[MAXPATHLEN+1];
  715.     
  716.     // Always verify this operation, since it is irreversible
  717.     if ( NXRunAlertPanel ( "Verify",
  718.         "This action is irreversible.  Are you sure you want to delete the group \"%s\" ?",
  719.         "Delete", "Cancel", NULL, [currentGroup groupName] )
  720.             != NX_ALERTDEFAULT )
  721.         return ( self );
  722.     
  723.     i = [self indexOf:currentGroup];
  724.     [self removeObject:currentGroup];
  725.     [[viewer browser] showGroup:nil];
  726.     [self setNeedsShow:YES];
  727.  
  728.     [self sortGroups];
  729.     [self setChanged:YES];
  730.     
  731.     if ( [self count] ) {
  732.         if ( i>0 ) i--;
  733.         [self makeCurrentGroup:[self objectAt:i]];
  734.     } else
  735.         [self makeCurrentGroup:nil];
  736.     
  737.     // Remove the group from the folder
  738.     if ( filename ) {
  739.         sprintf ( path, "%s/%s.%s", filename, [group groupName], GROUP_EXT );
  740.         unlink ( path );
  741.     }
  742.  
  743.     [changedGroups removeObject:group];
  744.     [self groupRemoved];
  745.     [group free];
  746.         
  747.     return ( self );
  748. }
  749.  
  750.  
  751.  
  752. - launchCurrentGroup:sender
  753. /*
  754.     Launch all items in currentGroup that are tagged for groupLaunch
  755. */
  756. {
  757.     int i, count;
  758.     
  759.     [NXApp deactivateSelf];
  760.     
  761.     for ( i=0, count=[currentGroup count]; i<count; i++ )
  762.         if ( [[currentGroup objectAt:i] isGroupLaunch] )
  763.             [[currentGroup objectAt:i] launch];
  764.     
  765.     // Make sure the cells get redrawn
  766.     [self setNeedsShow:YES];    
  767.     return ( self );
  768. }
  769.  
  770.  
  771.  
  772. - cleanUpCurrentGroup:sender
  773. {
  774.     // Forward to current group
  775.     [currentGroup cleanUp:sender];
  776.     return ( self );
  777. }
  778.  
  779.  
  780.  
  781. - groupCalled:(const char *)groupName
  782. {
  783.     int i, count;
  784.     
  785.     if ( !groupName )
  786.         return ( nil );
  787.  
  788.     for ( i=0, count=[self count]; i<count; i++ )
  789.         if ( !strcmp ( [[self objectAt:i] groupName], groupName ) )
  790.             return ( [self objectAt:i] );
  791.             
  792.     return ( nil );
  793. }
  794.  
  795.  
  796.  
  797. - (BOOL)groupExists:(const char *)groupName
  798. {
  799.     return ( [self groupCalled:groupName] ? YES : NO );
  800. }
  801.  
  802.  
  803.  
  804. - renameGroup:aGroup to:(const char *)aString
  805. {
  806.     id g;
  807.     char path[MAXPATHLEN+1];
  808.     
  809.     if ( (g =[self groupCalled:aString]) && g != aGroup )
  810.         return ( nil ); // This name is already in use
  811.  
  812.     // Remove the old group file
  813.     if ( filename ) {
  814.         sprintf ( path, "%s/%s.%s", filename, [aGroup groupName], GROUP_EXT );
  815.         unlink ( path );
  816.     }
  817.     
  818.     // Rename the group
  819.     [aGroup setGroupName:aString];
  820.     [aGroup setChanged:YES];
  821.         
  822.     fFlags.needsSort = YES; // Force groups to be sorted
  823.     [self sortGroups];
  824.     
  825.     if ( aGroup == currentGroup && [viewer isVisible] ) {
  826.         [[viewer groupMenuCover] setTitle:aString];
  827.         [viewer display];
  828.     }
  829.         
  830.     return ( self );
  831. }
  832.  
  833.  
  834.  
  835. - selectGroupFromMenu:sender
  836. {
  837.     [self makeCurrentGroup:[self groupCalled:[[viewer groupMenu] selectedItem]]];
  838.     return ( self );
  839. }
  840.  
  841.  
  842.  
  843. - makeCurrentGroup:aGroup
  844. {
  845.     id oldGroup;
  846.     
  847.     if ( aGroup == currentGroup ) // No change
  848.         return ( self );
  849.  
  850.     oldGroup = currentGroup;
  851.     currentGroup = aGroup;
  852.  
  853.     [folderController updateMenu:self];
  854.  
  855.     if ( currentGroup ) currentGroupTag = [currentGroup tag];
  856.         else currentGroupTag = 0;
  857.     
  858.     if ( [currentGroup drawMode] == IC_DRAW_UNKNOWN )
  859.         [currentGroup setDrawMode:IC_LARGE_BROWSE];
  860.         
  861.     fFlags.needsLoadBrowser = YES;
  862.     [self setNeedsShow:YES];
  863.     
  864.     if ( currentGroup != oldGroup && oldGroup != nil )  [self setChanged:YES];
  865.     [self updateInspector:self];
  866.     return ( self );
  867. }
  868.  
  869.  
  870.  
  871. int compareGroups ( Group **group1, Group **group2 )
  872. /*
  873.     Compare the two groups for sorting.  First, compares their key equivalents, then compares them alphabetically.  Returns less than 0 if group1<group2, 0 if group1==group2, greater than 0 if group1>group2.
  874.     THIS METHOD IS CURRENTLY BROKEN
  875. */
  876. {
  877.     int compare = 0;
  878.     
  879.     if ( [*group1 keyEquivalent] < [*group2 keyEquivalent] ) {
  880.         if ( ![*group1 keyEquivalent] ) compare = 1; // Group1 > Group2
  881.             else compare = -1; // Group1 < Group2
  882.     } else
  883.     
  884.     if ( [*group1 keyEquivalent] > [*group2 keyEquivalent] ) {
  885.         compare = 1; // Group1 > Group2
  886.     } else
  887.     
  888.     if ( [*group1 keyEquivalent] == [*group2 keyEquivalent] ) {
  889.         compare = NXOrderStrings ( [*group1 groupName], [*group2 groupName],
  890.                     NO, -1, NULL );\
  891.     }
  892.  
  893.     return ( compare );
  894. }
  895.  
  896.  
  897.  
  898. - sortGroups
  899. {
  900.     int i;
  901.     id    groupMenu = [viewer groupMenu];
  902.         
  903.     // Remove all items from [viewer groupMenu]
  904.     while ( [[[groupMenu itemList] cellList] count] ) [groupMenu removeItemAt:0];
  905.         
  906.     if ( fFlags.needsSort ) {
  907.         // qsort ( dataPtr, numElements, sizeof ( id * ), compareGroups );
  908.         // qsort() seems to choke on something, so we use our own buble sort:
  909.         // (this is ok since the number of groups is generally small)
  910.         int j,k, compare;
  911.         id temp;
  912.         for ( j=0; j<numElements; j++ ) {
  913.             for ( k=0; k<numElements-1; k++ ) {
  914.                 compare = compareGroups ( (dataPtr + k), (dataPtr + k+1) );
  915.                 if ( compare > 0 ) { // group k > group k+1
  916.                     // Do nothing -- already in proper order
  917.                 } else
  918.                 if ( compare == 0 ) { // group k = group k+1
  919.                     // Do nothing -- already in proper order
  920.                 } else
  921.                 if ( compare < 0 ) { // group k < group k+1
  922.                     // Swap group k and group k+1
  923.                     temp = *(dataPtr + k);
  924.                     *(dataPtr + k) = *(dataPtr + k+1);
  925.                     *(dataPtr + k+1) = temp;
  926.                 }
  927.             }
  928.         }    
  929.         fFlags.needsSort = NO;
  930.     }
  931.     
  932.     i = numElements;
  933.     while ( i-- ) { // Add the groups to the menu in reverse order
  934.         [groupMenu addItem:[[self objectAt:i] groupName]
  935.             action:@selector(selectGroupFromMenu:)
  936.             keyEquivalent:[[self objectAt:i] keyEquivalent]];
  937.     }
  938.     [groupMenu sizeToFit];
  939.     return ( self );
  940. }
  941.  
  942.  
  943.  
  944. // -------------------------------------------------------------------------
  945. //   Responding to key events
  946. // -------------------------------------------------------------------------
  947.  
  948.  
  949. - keyDown:(NXEvent *)event
  950. /*
  951.     Respond to the user presssing a key.  If the key can be handled by the folder, returns self.  If the key has no meaning to the folder, returns nil.
  952. */
  953. {
  954.     int i = 0;
  955.     
  956.     if ( event->data.key.charSet == NX_ASCIISET && !event->flags ) {
  957.         switch ( event->data.key.charCode ) {
  958.             case NX_CR:
  959.             case NX_RETURN:
  960.                 [currentGroup launchSelectedItems:self];
  961.                 return ( self );
  962.                 
  963.             case NX_DELETE: // Delete removes selected items
  964.                 [currentGroup removeSelectedItems:self];
  965.                 return ( self );
  966.         }
  967.     } else
  968.     
  969.     if ( event->data.key.charSet == NX_ASCIISET
  970.         && event->flags == NX_NUMERICPADMASK
  971.         && event->data.key.charCode == 0x03 ) { // Enter key on key pad
  972.             [currentGroup launchSelectedItems:self];
  973.             return ( self );
  974.     }
  975.     
  976.     else {
  977.         switch ( event->data.key.charCode ) {
  978.         
  979.             case 172: // Left arrow -- move to previous group
  980.                 if ( (i=[self indexOf:currentGroup]) < numElements-1 )
  981.                     [self makeCurrentGroup:[self objectAt:++i]];
  982.                 return ( self );
  983.                 
  984.             case 174:    // Right arrow -- move to next group
  985.                 if ( (i=[self indexOf:currentGroup]) > 0 )
  986.                     [self makeCurrentGroup:[self objectAt:--i]];
  987.                 return ( self );
  988.                 
  989.             case 173:    // Up arrow -- move selection up
  990.                 [[viewer browser] selectUp:self];
  991.                 return ( self );
  992.             
  993.             case 175: // Down arrow -- move selection down
  994.                 [[viewer browser] selectDown:self];
  995.                 return ( self );
  996.         }
  997.         
  998.     }
  999.     
  1000.     return ( nil );
  1001. }    
  1002.  
  1003.  
  1004.  
  1005. // -------------------------------------------------------------------------
  1006. //   Window Delegate methods -- sent by our Viewer
  1007. // -------------------------------------------------------------------------
  1008.  
  1009.  
  1010. - windowWillClose:sender
  1011. {
  1012.     int ret;
  1013.     
  1014.     // Ask to save the Folder if it has been changed before closin it
  1015.     if ( [self isChanged] ) {
  1016.         ret = NXRunAlertPanel ( "Alert",
  1017.             "Folder: %s hasn't been saved.  Do you want to save it?",
  1018.             "Save", "Don't Save", "Cancel", filename ? filename : "UNTITLED" );
  1019.         switch ( ret ) {
  1020.             case NX_ALERTDEFAULT: // Save
  1021.                 [folderController saveFolder:self];
  1022.                 break;
  1023.             case NX_ALERTALTERNATE: // Don't Save
  1024.                 break;
  1025.             case NX_ALERTOTHER: // Cancel
  1026.                 return ( nil );
  1027.         }
  1028.     }
  1029.  
  1030.     // Remove the folder only once we get back to the event loop...
  1031.     // (in case anybody is still going to send messages to it)
  1032.     [folderController perform:@selector(removeFolder:)
  1033.         with:self
  1034.         afterDelay:0
  1035.         cancelPrevious:NO];
  1036.  
  1037.     return ( self );
  1038. }
  1039.  
  1040.  
  1041.  
  1042. #define VIEWER_MIN_W    100
  1043. #define VIEWER_MIN_H    145
  1044. - windowWillResize:sender toSize:(NXSize *)size
  1045. {
  1046.     if ( size->width < VIEWER_MIN_W ) size->width = VIEWER_MIN_W;
  1047.     if ( size->height < VIEWER_MIN_H ) size->height = VIEWER_MIN_H;
  1048.     return ( self );
  1049. }
  1050.  
  1051.  
  1052.  
  1053. - windowDidBecomeKey:sender
  1054. {
  1055.     if ( [NXApp mainWindow] != sender ) {
  1056.         [[NXApp mainWindow] resignMainWindow];
  1057.         [sender becomeMainWindow];
  1058.     }
  1059.     [folderController makeKeyFolder:self];
  1060.     return ( self );
  1061. }
  1062.  
  1063.  
  1064.  
  1065. - windowDidResignKey:sender
  1066. {
  1067.     return ( self );
  1068. }
  1069.  
  1070.  
  1071.  
  1072. - windowDidMiniaturize:sender
  1073. {
  1074.     return ( self );
  1075. }
  1076.  
  1077.  
  1078.  
  1079. - windowDidMove:sender
  1080. {
  1081.     NXRect frame;
  1082.     [sender getFrame:&frame];
  1083.     [self setViewerFrame:&frame];
  1084.     return ( self );
  1085. }
  1086.  
  1087.  
  1088.  
  1089. - windowDidResize:sender
  1090. {
  1091.     NXRect frame;
  1092.     [sender getFrame:&frame];
  1093.     [self setViewerFrame:&frame];
  1094.     return ( self );
  1095. }
  1096.  
  1097.  
  1098.  
  1099. // -------------------------------------------------------------------------
  1100. //   GroupBrowser Delegate methods
  1101. // -------------------------------------------------------------------------
  1102.  
  1103.  
  1104. - browserSelectionChanged:sender
  1105. {
  1106.     if ( inspectorMode == INSPECT_ITEM ) [inspector update];
  1107.     return ( self );
  1108. }
  1109.  
  1110.  
  1111.  
  1112. @end
  1113.  
  1114.  
  1115.  
  1116. // selectGroups() function used by scandir in -readGroups
  1117. int selectGroups ( struct direct *dp )
  1118. /*
  1119.     Returns 1 if dp->d_name is a group file, 0 if not
  1120. */
  1121. {
  1122.     char *ext;
  1123.     
  1124.     ext = rindex ( dp->d_name, '.' ); // find last '.'
  1125.     if ( !ext ) return ( 0 ); // This one doesn't have any extension
  1126.         else ext++; // extension starts right after last '.'
  1127.         
  1128.     return ( !strcmp ( ext, GROUP_EXT ) ? 1 : 0 );
  1129. }
  1130.  
  1131.  
  1132.